package com.example.auth.endpoint;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.example.auth.constant.PM;
import com.example.auth.vo.Pagination;
import com.example.auth.vo.UserToken;
import com.example.starter.constant.SecurityConstant;
import com.example.starter.security.configure.SecurityProperties;
import com.example.starter.security.pojo.ExampleUser;
import com.example.starter.util.Response;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

@RequiredArgsConstructor
@RestController
@RequestMapping("/oauth")
public class AuthTokenEndpoint {

    private final ConsumerTokenServices consumerTokenServices;
    private final RedisTemplate<String, Object> redisTemplate;
    private final SecurityProperties properties;
    private final TokenEndpoint tokenEndpoint;
    private final TokenStore tokenStore;

    @GetMapping("/token")
    public Response<OAuth2AccessToken> getOAuthToken(Principal principal,
                                                     @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(principal, parameters);
        return Response.ok(accessToken.getBody());
    }

    @PostMapping("/token")
    public Response<OAuth2AccessToken> postOAuthToken(Principal principal,
                                                      @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        ResponseEntity<OAuth2AccessToken> accessToken = tokenEndpoint.postAccessToken(principal, parameters);
        return Response.ok(accessToken.getBody());
    }

    @PostMapping("/logout")
    public Response<Boolean> logout(@RequestHeader(value = SecurityConstant.AUTHORIZATION, required = false) String authorization) {
        if (StrUtil.isNotBlank(authorization)) {
            consumerTokenServices.revokeToken(authorization.replace("Bearer ", ""));
        }
        return Response.ok();
    }

    @PreAuthorize("hasAuthority('" + PM.REVOKE_TOKEN + "')")
    @PostMapping("/revoke/token")
    public Response<Boolean> revokeToken(@RequestParam("accessToken") String accessToken) {
        consumerTokenServices.revokeToken(accessToken);
        return Response.ok();
    }

    @PreAuthorize("hasAuthority('" + PM.TOKEN_READ + "')")
    @GetMapping("/pagination")
    public Response<Pagination<UserToken>> listWithPagination(Pageable pageable) {
        Set<String> keys = redisTemplate.keys(StrUtil.concat(true, properties.getPrefix(), "access:*"));
        assert keys != null;
        if (ObjectUtil.isEmpty(keys)) {
            return Response.ok();
        }
        Pagination<UserToken> pagination = new Pagination<>();
        pagination.setTotal(keys.size());
        List<UserToken> records = new ArrayList<>();
        keys.stream()
                .skip(pageable.getOffset())
                .limit(pageable.getPageSize())
                        .forEach(token -> {
                            String accessToken = token.replace(StrUtil.concat(true, properties.getPrefix(), "access:"), "");
                            OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
                            OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
                            UserToken userToken = new UserToken();
                            ExampleUser user = (ExampleUser) oAuth2Authentication.getPrincipal();
                            userToken.setId(user.getId());
                            userToken.setUsername(user.getUsername());
                            userToken.setPhone(user.getPhone());
                            userToken.setClientId(oAuth2Authentication.getOAuth2Request().getClientId());
                            userToken.setExpire(oAuth2AccessToken.getExpiresIn());
                            userToken.setAccessToken(accessToken);
                            userToken.setType(oAuth2AccessToken.getTokenType());
                            records.add(userToken);
                        });
        pagination.setCurrent(pageable.getPageNumber() + 1);
        pagination.setSize(pageable.getPageSize());
        pagination.setRecords(records);
        return Response.ok(pagination);
    }

}
